<template>
  <div id="container" @click.stop="hideFn" v-loading="loading">
    <!--    节点操作菜单   -->
    <header>
      <el-tooltip class="item" effect="dark" content="放大" placement="bottom">
        <i class="el-icon-zoom-in" @click="zoomFn(0.2)" />
      </el-tooltip>
      <el-tooltip class="item" effect="dark" content="缩小" placement="bottom">
        <i class="el-icon-zoom-out" @click="zoomFn(-0.2)" />
      </el-tooltip>
      <el-tooltip
        class="item"
        effect="dark"
        :content="isFullscreen ? '退出全屏' : '全屏'"
        placement="bottom"
      >
        <svg-icon
          style="
            width: 20px;
            height: 20px;
            display: inline-block;
            margin: 12px 10px 0;
          "
          :icon-class="isFullscreen ? 'exit-fullscreen' : 'fullscreen'"
          @click="fullScreenFn"
        />
      </el-tooltip>
      <!--      <el-tooltip-->
      <!--          class="item"-->
      <!--          effect="dark"-->
      <!--          content="新的标签页展示"-->
      <!--          placement="bottom"-->
      <!--      >-->
      <!--        <i class="el-icon-rank" @click="toNewPage()" />-->
      <!--      </el-tooltip>-->
      <el-tooltip class="item" effect="dark" content="重置" placement="bottom">
        <i class="el-icon-refresh-right" @click="startFn()" />
      </el-tooltip>
    </header>
    <!--    画布容器    -->
    <MenuBar
      v-if="showContextMenu"
      ref="menuBar"
      :bizDate="bizDate"
      :nodeType="nodeType"
      @callBack="contextMenuFn"
    />
    <div id="draw-cot" style="min-width: 1600px; min-height: 800px" />
  </div>
</template>

<script>
import { Graph, Path } from "@antv/x6";
import { Group } from "./components/shape";
import "@antv/x6-vue-shape";
import taskDefApi from "@/api/cycleJob/taskDef";

import database from "./components/database.vue";
import MenuBar from "./components/menuBar.vue";

export default {
  name: "DependGraph",
  components: { MenuBar },
  props: {
    nodeCode: {
      type: String,
      required: true,
    },
    nodeType: String,
    bizDate: String,
    appName: String,
  },
  data() {
    return {
      graph: "",
      timer: "",
      isLock: false,
      showContextMenu: false,
      nodeMessage: null,
      statusList: [],
      //状态变化连接的边
      connectingEdge: null,
      x: "",
      y: "",
      graphOffsetX: 690,
      graphOffsetY: 365,
      isFullscreen: false,
      taskNum: 0,
      lastJobY: 0,
      jobTaskNum: 0,
      hasJobDep: false,
      loading: false,
      zNum: 1,
    };
  },
  mounted() {
    // console.log('传过来的类型', this.nodeType)
    // console.log('传过来的日期', this.bizDate)
    // console.log('传过来的任务编码', this.nodeCode)
    if (this.appName === "job") {
      this.graphOffsetX = 530;
    }
    if (this.appName === "task") {
      this.graphOffsetX = 125;
    }
    // 初始化 graph
    this.initGraph();
    // 按钮绑定
    this.keyBindFn();
    // 执行
    this.startFn();
  },
  watch: {
    nodeCode(newVal, oldVal) {
      if (newVal !== oldVal) {
        this.startFn();
      }
    },
  },
  methods: {
    hideFn() {
      this.showContextMenu = false;
    },
    initGraph() {
      //注册作业节点
      Graph.registerNode("job", Group, true);
      // 注册节点
      //将vue组件注册到系统中
      Graph.registerNode(
        "dag-node",
        {
          inherit: "vue-shape",
          width: 180,
          height: 36,
          component: {
            template: `<database />`,
            components: {
              database,
            },
          },
          ports: {
            groups: {
              top: {
                position: "top",
                attrs: {
                  circle: {
                    r: 4,
                    magnet: true,
                    stroke: "#C2C8D5",
                    strokeWidth: 1,
                    fill: "#fff",
                  },
                },
              },
              bottom: {
                position: "bottom",
                attrs: {
                  circle: {
                    r: 4,
                    magnet: true,
                    stroke: "#C2C8D5",
                    strokeWidth: 1,
                    fill: "#fff",
                  },
                },
              },
            },
          },
        },
        true
      );

      Graph.registerEdge(
        "dag-edge",
        {
          inherit: "edge",
          attrs: {
            line: {
              stroke: "#C2C8D5",
              strokeWidth: 2,
              targetMarker: {
                name: "block",
                width: 12,
                height: 8,
              },
            },
          },
        },
        true
      );
      Graph.registerConnector(
        "algo-connector",
        (s, e) => {
          const offset = 4;
          const deltaY = Math.abs(e.y - s.y);
          const control = Math.floor((deltaY / 3) * 2);

          const v1 = { x: s.x, y: s.y + offset + control };
          const v2 = { x: e.x, y: e.y - offset - control };

          return Path.normalize(
            `M ${s.x} ${s.y}
           L ${s.x} ${s.y + offset}
           C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${e.x} ${e.y - offset}
           L ${e.x} ${e.y}
          `
          );
        },
        true
      );

      const graph = new Graph({
        container: document.getElementById("draw-cot"),
        //scroller: true,
        sorting: "approx",
        //interacting: true,
        panning: {
          enabled: true,
          eventTypes: ["leftMouseDown", "mouseWheel"],
        },
        mousewheel: {
          enabled: true,
          modifiers: "ctrl",
          factor: 1.1,
          maxScale: 1.5,
          minScale: 0.5,
        },
        highlighting: {
          magnetAdsorbed: {
            name: "stroke",
            args: {
              attrs: {
                fill: "#fff",
                stroke: "#31d0c6",
                strokeWidth: 4,
              },
            },
          },
        },
        connecting: {
          anchor: "orth",
          connector: "rounded",
          connectionPoint: "boundary",
          router: {
            name: "manhattan",
          },
        },
        // translating: {
        //   restrict(view) {
        //     const cell = view.cell;
        //     if (cell.isNode()) {
        //       const parent = cell.getParent();
        //       if (parent) {
        //         return parent.getBBox();
        //       }
        //     }
        //     return null;
        //   },
        // },
        selecting: {
          enabled: true,
          multiple: true,
          rubberEdge: true,
          rubberNode: true,
          modifiers: "shift",
          rubberband: true,
        },
        keyboard: true,
        clipboard: true,
        history: true,
      });
      this.graph = graph;

      //监听边鼠标右键点击事件
      graph.on("edge:contextmenu", ({ e, x, y, edge, view }) => {
        // console.log(x, y, view);
        this.showContextMenu = true;
        this.$nextTick(() => {
          this.$refs.menuBar.initFn(e.offsetX, e.offsetY, {
            type: "edge",
            item: edge,
          });
        });
      });

      //监听边节点右键点击事件
      graph.on("node:contextmenu", ({ e, x, y, node, view }) => {
        // console.log(e, x, y, view);
        this.showContextMenu = true;
        this.$nextTick(() => {
          const p = graph.localToPage(x, y);
          if (!document.fullscreenElement) {
            this.x = p.x - this.graphOffsetX;
            this.y = p.y - this.graphOffsetY;
            this.$refs.menuBar.initFn(this.x, this.y, {
              type: `${this.nodeType}-node`,
              item: node,
            });
          } else {
            //console.log('全屏------------')
            this.$refs.menuBar.initFn(p.x, p.y, {
              type: `${this.nodeType}-node`,
              item: node,
            });
          }
          //console.log("转换后坐标", p.x, p.y)
        });
      });

      //监听节点数据变化
      graph.on("node:change:data", ({ node }) => {
        const { status } = node.getData();
        if (status === "running") {
          this.connectingEdge?.attr("line/strokeDasharray", 5);
          this.connectingEdge?.attr(
            "line/style/animation",
            "running-line 30s infinite linear"
          );
        } else {
          this.connectingEdge?.attr("line/strokeDasharray", "");
          this.connectingEdge?.attr("line/style/animation", "");
        }
      });

      //监听折叠操作事件
      graph.on("node:collapse", ({ node }) => {
        node.toggleCollapse();
        const collapsed = node.isCollapsed();
        const collapse = (parent) => {
          const cells = parent.getChildren();
          if (cells) {
            cells.forEach((cell) => {
              if (collapsed) {
                cell.hide();
              } else {
                cell.show();
              }
              if (cell instanceof Group) {
                if (!cell.isCollapsed()) {
                  collapse(cell);
                }
              }
            });
          }
        };
        collapse(node);
      });

      //监听子节点个数变化进行自适应
      this.graph.on("node:change:size", ({ node, options }) => {
        if (options.skipParentHandler) {
          return;
        }

        const children = node.getChildren();
        if (children && children.length) {
          node.prop("originSize", node.getSize());
        }
      });

      this.graph.on("node:change:position", ({ node, options }) => {
        if (options.skipParentHandler) {
          return;
        }
        const children = node.getChildren();
        if (children && children.length) {
          node.prop("originPosition", node.getPosition());
        }

        const parent = node.getParent();
        if (parent && parent.isNode()) {
          let originSize = parent.prop("originSize");
          if (originSize == null) {
            parent.prop("originSize", parent.getSize());
          }
          originSize = parent.prop("originSize");

          let originPosition = parent.prop("originPosition");
          if (originPosition == null) {
            parent.prop("originPosition", parent.getPosition());
          }
          originPosition = parent.prop("originPosition");

          let x = originPosition.x;
          let y = originPosition.y;
          let cornerX = originPosition.x + originSize.width;
          let cornerY = originPosition.y + originSize.height;
          let hasChange = false;

          const children = parent.getChildren();
          if (children) {
            children.forEach((child) => {
              const bbox = child.getBBox();
              const corner = bbox.getCorner();

              if (bbox.x < x) {
                x = bbox.x;
                hasChange = true;
              }

              if (bbox.y < y) {
                y = bbox.y;
                hasChange = true;
              }

              if (corner.x > cornerX) {
                cornerX = corner.x;
                hasChange = true;
              }

              if (corner.y > cornerY) {
                cornerY = corner.y;
                hasChange = true;
              }
            });
          }

          if (hasChange) {
            parent.prop(
              {
                position: { x, y },
                size: { width: cornerX - x, height: cornerY - y },
              },
              // Note that we also pass a flag so that we know we shouldn't
              // adjust the `originPosition` and `originSize` in our handlers.
              { skipParentHandler: true }
            );
          }
        }
      });
    },
    // async showNodeStatus(statusList) {
    //   console.log("初始化状态列表", statusList);
    //   const newStatus = statusList.shift();
    //   newStatus?.forEach((item) => {
    //     const { id, edgeId, status } = item;
    //     const node = this.graph.getCellById(id);
    //     this.connectingEdge = this.graph.getCellById(edgeId);
    //     console.log("进入的边", this.connectingEdge);
    //     if (item.status === "running") {
    //       this.connectingEdge?.attr("line/stroke", "#7c68fc");
    //     } else if (item.status === "success") {
    //       this.connectingEdge?.attr("line/stroke", "#52c41a");
    //     } else if (item.status === "failed") {
    //       this.connectingEdge?.attr("line/stroke", "#ff4d4f");
    //     } else if (item.status === "warning") {
    //       this.connectingEdge?.attr("line/stroke", "yellow");
    //     }
    //     const data = node.getData();
    //     node.setData({
    //       ...data,
    //       status: status,
    //     });
    //   });
    //   this.timer = setTimeout(() => {
    //     if (statusList.length !== 0) {
    //       this.showNodeStatus(statusList);
    //     }
    //   }, 1500);
    // },
    // 初始化节点/边
    init(initNode) {
      const cells = [];
      cells.push(this.graph.createNode(initNode));
      this.graph.resetCells(cells);
      // this.centerFn();
      // this.graph.center()
      // this.graph.centerContent();
    },
    show(data = [], currentNode, depTag) {
      this.zNum++;
      const cells = [];
      if (data) {
        data = this.formatJsonData(data, currentNode, depTag);
        data.forEach((item) => {
          if (item.shape === "dag-edge") {
            cells.push(this.graph.createEdge(item));
          } else {
            if (item.shape === "job") {
              const parentNode = this.createGroup(
                item.id,
                item.x,
                item.y,
                item.width,
                item.height,
                item.taskCode
              );
              this.graph.addNode(parentNode);
              const childList = item.data.children; //子节点
              const edgeList = item.data.edgeList; //边
              var d = this.graph.getNodes().map((node) => node.id);
              childList?.forEach((child) => {
                console.log("c", child);
                for (let i = 0; i < d.length; i++) {
                  if (d[i] == child.id) {
                    // if (this.graph.getCellById(d[i]).store.data.data.isRoot) {
                    //   this.graph.getCellById(
                    //     d[i]
                    //   ).store.data.data.isRoot = false;
                    // this.graph.getCellById(d[i]).update({
                    //     zIndex: 16 // 将zIndex更改为2
                    // });
                    this.graph.getCellById(d[i]).zIndex += 16 * this.zNum;
                    this.graph.getCellById(d[i]).setPosition({
                      x: child.x,
                      y: child.y,
                    });
                    // }
                  }
                }

                parentNode.addChild(this.graph.createNode(child));
              });
              //console.log("所有边", edgeList);
              edgeList?.forEach((edge) => {
                cells.push(this.graph.createEdge(edge));
              });
              //使作业节点大小根据子节点个数变化
              let childrenNum = parentNode.getChildren().length;
              parentNode.prop(
                {
                  size: { width: 250, height: 90 * childrenNum },
                },
                { skipParentHandler: true }
              );
            }
            delete item.component;
            cells.push(this.graph.createNode(item));
          }
        });
        cells.forEach((cell) => {
          this.graph.addCell(cell);
        });
        // console.log(this.graph.getCellById(d[0]));
      }
      // this.centerFn();
      // this.graph.center()
      // this.graph.centerContent();
    },
    createGroup(id, x, y, width, height, text) {
      const group = new Group({
        id,
        x,
        y,
        width,
        height,
        attrs: {
          label: { text },
        },
      });
      group.toggleCollapse(false);
      this.graph.addNode(group);
      return group;
    },
    zoomFn(num) {
      this.graph.zoom(num);
    },
    setFullscreen(element) {
      const el =
        element instanceof HTMLElement ? element : document.documentElement;
      const rfs =
        el.requestFullscreen ||
        el.webkitRequestFullscreen ||
        el.mozRequestFullScreen ||
        el.msRequestFullscreen;
      if (rfs) {
        rfs.call(el);
      } else if (window.ActiveXObject) {
        const ws = new ActiveXObject("WScript.Shell");
        ws && ws.SendKeys("{F11}");
      }
    },
    exitFullscreen() {
      const efs =
        document.exitFullscreen ||
        document.webkitExitFullscreen ||
        document.mozCancelFullScreen ||
        document.msExitFullscreen;
      if (efs) {
        efs.call(document);
      } else if (window.ActiveXObject) {
        const ws = new ActiveXObject("WScript.Shell");
        ws && ws.SendKeys("{F11}");
      }
    },
    fullScreenFn() {
      if (document.fullscreenElement) {
        // 如果当前已经是全屏状态，则退出全屏
        // document.exitFullscreen();
        this.exitFullscreen();
        this.isFullscreen = false;
      } else {
        const container = document.getElementById("container");
        // 否则，进入全屏
        //container.requestFullscreen();
        this.setFullscreen(container);
        this.isFullscreen = true;
        container.style.background = "white";
      }
    },
    centerFn() {
      const num = 1 - this.graph.zoom();
      num > 1 ? this.graph.zoom(num * -1) : this.graph.zoom(num);
      const nodes = this.graph.getNodes();
      const graphBBox = {
        x: Math.min(...nodes.map((node) => node.getBBox().x)),
        y: Math.min(...nodes.map((node) => node.getBBox().y)),
        width: Math.max(...nodes.map((node) => node.getBBox().width)),
        height: Math.max(...nodes.map((node) => node.getBBox().height)),
      };
      // 计算居中位置
      const centerX = graphBBox.width / 2 - graphBBox.x;
      const centerY = graphBBox.height / 2 - graphBBox.y;
      // 将整个图形移动到居中位置
      this.graph.translate(centerX, centerY);
      // this.graph.center()
      //this.graph.centerContent();
    },
    async startFn(item) {
      this.timer && clearTimeout(this.timer);
      //请求节点详细信息
      if (this.nodeType === "taskDef") {
        await taskDefApi
          .initTaskDefInfo(this.nodeCode)
          .then((res) => {
            //console.log("后端返回节点数据(无日期)", res)
            this.nodeMessage = res;
            this.nodeMessage = this.formatJsonData(res, null, 0, true);
            //console.log('处理后的nodeMessage', this.nodeMessage)
          })
          .catch((err) => {
            console.log(err);
          });
      } else {
        await taskDefApi
          .initTaskInfo(this.nodeCode, this.bizDate)
          .then((res) => {
            //console.log("后端返回节点数据", res)
            this.nodeMessage = res;
            this.nodeMessage = this.formatJsonData(res, null, 0, true);
            //console.log('处理后的nodeMessage', this.nodeMessage)
          })
          .catch((err) => {
            console.log(err);
          });
      }
      // this.nodeMessage.data.isRoot = true
      this.init(item || this.nodeMessage);
      //await this.showNodeStatus(Object.assign([], this.statusList[0]));
    },
    keyBindFn() {
      // copy cut paste
      this.graph.bindKey(["meta+c", "ctrl+c"], () => {
        const cells = this.graph.getSelectedCells();
        if (cells.length) {
          this.graph.copy(cells);
        }
        return false;
      });
      this.graph.bindKey(["meta+x", "ctrl+x"], () => {
        const cells = this.graph.getSelectedCells();
        if (cells.length) {
          this.graph.cut(cells);
        }
        return false;
      });
      this.graph.bindKey(["meta+v", "ctrl+v"], () => {
        if (!this.graph.isClipboardEmpty()) {
          const cells = this.graph.paste({ offset: 32 });
          this.graph.cleanSelection();
          this.graph.select(cells);
        }
        return false;
      });

      // undo redo
      this.graph.bindKey(["meta+z", "ctrl+z"], () => {
        if (this.graph.history.canUndo()) {
          this.graph.history.undo();
        }
        return false;
      });
      // delete
      this.graph.bindKey(["delete"], () => {
        const select = this.graph.getSelectedCells();
        select?.forEach((item) => {
          if (/edge/.test(item.shape)) {
            this.graph.removeEdge(item.id);
          } else {
            this.graph.removeNode(item.id);
          }
        });
        return false;
      });
    },
    contextMenuFn(type, node) {
      switch (type) {
        case "remove":
          if (node.type === "edge") {
            this.graph.removeEdge(node.item.id);
          } else if (node.type === "node") {
            this.graph.removeNode(node.item.id);
          }
          break;
        case "source":
          this.$refs.dialogMysql.visible = true;
          this.$refs.dialogMysql.init(node);
          break;
      }
      this.showContextMenu = false;
    },
    addNode(option) {
      const p = this.graph.pageToLocal(option.x, option.y);
      this.graph.addNode(Object.assign({}, option, p));
    },
    formatJsonData(nodesData, currentNode = null, depTag, rootTag = false) {
      this.taskNum = 0;
      this.lastJobY = 0;
      this.jobTaskNum = 0;
      if (Array.isArray(nodesData)) {
        if (nodesData.some((nodeData) => nodeData.shape === "job")) {
          this.taskNum = 1;
          this.hasJobDep = true;
        } else {
          this.hasJobDep = false;
        }
        nodesData = nodesData.map((nodeData) =>
          this.formatNodeData(nodeData, currentNode, depTag, rootTag)
        );
      } else {
        nodesData = this.formatNodeData(
          nodesData,
          currentNode,
          depTag,
          rootTag
        );
      }
      //console.log('任务节点总数', this.taskNum)
      return nodesData;
    },
    formatNodeData(nodeData, currentNode, depTag, rootTag) {
      if (nodeData.shape === "task") {
        nodeData = this.formatTaskNodeData(
          nodeData,
          currentNode,
          depTag,
          rootTag
        );
        this.taskNum++;
      } else if (nodeData.shape === "job") {
        nodeData.shape = "job";
        nodeData.x = currentNode.position().x - 320;
        nodeData.y = this.lastJobY;
        let currentJobHeight = nodeData.data.children?.length * 90;
        this.lastJobY = nodeData.y + currentJobHeight + 25;
        nodeData.width = 250;
        nodeData.height = 240;
        nodeData.zIndex = 1;
        nodeData.data.children = nodeData.data.children.map((child) =>
          this.formatTaskNodeData(child, nodeData)
        );
        this.jobTaskNum = 0;
      } else {
        nodeData.shape = "dag-edge";
        nodeData.zIndex = 10;
      }
      return nodeData;
    },
    formatTaskNodeData(taskNodeData, currentNode, depTag, rootTag) {
      taskNodeData.shape = "dag-node";
      taskNodeData.data = {};
      if (rootTag) {
        // console.log('根节点')
        taskNodeData.x = 0;
        taskNodeData.y = 0;
        taskNodeData.data.isRoot = true;
      } else {
        if (currentNode.position) {
          // console.log('当前的节点个数', this.taskNum)
          // console.log("来到上下层依赖", currentNode, currentNode.position().x, currentNode.position().y)
          if (this.hasJobDep) {
            taskNodeData.x = currentNode.position().x - 250;
            taskNodeData.y = currentNode.position().y - 50 * this.taskNum;
          } else {
            taskNodeData.y = currentNode.position().y + 50 * this.taskNum;
            if (depTag === 1) {
              taskNodeData.x = currentNode.position().x - 250;
            } else {
              taskNodeData.x = currentNode.position().x + 250;
            }
          }
        } else {
          taskNodeData.x = currentNode.x + 35;
          taskNodeData.y = currentNode.y + 50 + this.jobTaskNum * 60;
          this.jobTaskNum++;
        }
      }
      switch (taskNodeData.status) {
        case "1":
          taskNodeData.data.status = "default";
          taskNodeData.data.statusDesc = "待处理";
          break;
        case "2":
          taskNodeData.data.status = "running";
          taskNodeData.data.statusDesc = "处理中";
          break;
        case "3":
          taskNodeData.data.status = "default";
          taskNodeData.data.statusDesc = "任务暂停";
          break;
        case "4":
          taskNodeData.data.status = "success";
          taskNodeData.data.statusDesc = "处理完成";
          break;
        case "5":
          taskNodeData.data.status = "failed";
          taskNodeData.data.statusDesc = "处理失败";
          break;
        case "6":
          taskNodeData.data.status = "success";
          taskNodeData.data.statusDesc = "置虚";
          break;
        default:
          taskNodeData.data.status = "none";
      }
      taskNodeData.data.label = taskNodeData.taskCode;
      delete taskNodeData.status;
      delete taskNodeData.taskCode;
      //console.log("返回任务节点", taskNodeData);
      return taskNodeData;
    },
  },
};
</script>

<style lang="less" scoped>
header {
  display: flex;
  justify-content: flex-end;
  width: 100%;
  height: 40px;
  box-sizing: border-box;
}

header i {
  margin: 8px;
  font-size: 30px;
}
.x6-node-selected .node {
  border-color: #1890ff;
  border-radius: 2px;
  box-shadow: 0 0 0 4px #d4e8fe;
}

.x6-node-selected .node.success {
  border-color: #52c41a;
  border-radius: 2px;
  box-shadow: 0 0 0 4px #ccecc0;
}

.x6-node-selected .node.failed {
  border-color: #ff4d4f;
  border-radius: 2px;
  box-shadow: 0 0 0 4px #fedcdc;
}

.x6-edge:hover path:nth-child(2) {
  stroke: #1890ff;
  stroke-width: 1px;
}

.x6-edge-selected path:nth-child(2) {
  stroke: #1890ff;
  stroke-width: 1.5px !important;
}

#container {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

#container #draw-cot {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

::-webkit-scrollbar {
  width: 0;
}
</style>
